/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.common.util;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.crypto.SecureUtil;
import com.antherd.smcrypto.sm2.Sm2;
import com.antherd.smcrypto.sm3.Sm3;
import com.antherd.smcrypto.sm4.Sm4;
import com.antherd.smcrypto.sm4.Sm4Options;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.Security;
import java.util.Base64;

/**
 * 加密工具类，本框架目前使用 https://github.com/antherd/sm-crypto 项目中一些加解密方式
 * 使用小伙伴需要过等保密评相关，请在此处更改为自己的加密方法，或加密机，使用加密机同时需要替换公钥，私钥在内部无法导出，提供加密的方法
 * 如果不涉及到加密机方面的内容，请更改公私要为自己重新生成的，生成方式请看集成的sm-crypto主页
 *
 * @author yubaoshan
 * @date 2022/9/15 21:51
 */
@Slf4j
public class CommonCryptogramUtil {

    /**
     * AES密钥
     */
    private static final String AES_KEY = "mhxzkhl@2024gzx.";

    /**
     * AES密钥
     */
    private static final String AES_CFB_KEY = "F8993E59058EF40045B403857FADBF24";

    /**
     * 公钥
     */
    private static final String PUBLIC_KEY = "04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54";

    /**
     * 私钥
     */
    private static final String PRIVATE_KEY = "3037723d47292171677ec8bd7dc9af696c7472bc5f251b2cec07e65fdef22e25";

    /**
     * SM4的对称秘钥（生产环境需要改成自己使用的） 16 进制字符串，要求为 128 比特
     */
    private static final String KEY = "0123456789abcdeffedcba9876543210";

    public static String doSm1AesEncrypt(String plainText) {
        if (StringUtils.isEmpty(plainText)) {
            return null;
        }
        // aes算法的秘钥要求是16位、24位、32位
        int[] array = {16, 24, 32};
        if (!ArrayUtil.contains(array, AES_KEY.length())) {
            throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
        }
        return SecureUtil.aes(AES_KEY.getBytes(StandardCharsets.UTF_8)).encryptBase64(plainText, StandardCharsets.UTF_8);
    }

    public static String doSm1AesEncrypt(String plainText, String key, String iv) throws Exception {
        // 创建密钥和IV
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        // 添加BouncyCastleProvider
        Security.addProvider(new BouncyCastleProvider());
        // 创建Cipher实例并初始化
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        // 加密明文
        byte[] encryptedText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        // 将加密后的字节数组编码为Base64字符串
        return Base64.getEncoder().encodeToString(encryptedText);
    }

    public static String doSm1AesDecrypt(String plainText) {
        if (StringUtils.isEmpty(plainText)) {
            return null;
        }
        try {
            // aes算法的秘钥要求是16位、24位、32位
            int[] array = {16, 24, 32};
            if (!ArrayUtil.contains(array, AES_KEY.length())) {
                throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
            }
            return SecureUtil.aes(AES_KEY.getBytes(StandardCharsets.UTF_8)).decryptStr(plainText, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.warn("AES解密失败：{}", plainText);
            return plainText;
        }
    }

    public static String doAesCFBEncrypt(String plainText) {
        if (StringUtils.isEmpty(plainText)) {
            return null;
        }
        try {
            PaddedBufferedBlockCipher engine = new PaddedBufferedBlockCipher(new CFBBlockCipher(new AESEngine(), 128));
            engine.init(true, genAESCipherParameters());
            return org.apache.commons.codec.binary.Base64.encodeBase64String(cipherData(engine, plainText.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            return plainText;
        }
    }

    public static String doAesCFBDecrypt(String encryptText) {
        if (StringUtils.isEmpty(encryptText)) {
            return null;
        }
        try {
            PaddedBufferedBlockCipher engine = new PaddedBufferedBlockCipher(new CFBBlockCipher(new AESEngine(), 128));
            engine.init(false, genAESCipherParameters());
            return new String(cipherData(engine, org.apache.commons.codec.binary.Base64.decodeBase64(encryptText)), StandardCharsets.UTF_8);
        } catch (Exception e) {
            return encryptText;
        }
    }

    /**
     * 获取符合 AES 要求的密钥长度（16、24、32 字节）
     *
     * @param key 原始密钥
     * @return 调整后的密钥字节数组
     */
    private static byte[] getAESKey(String key) {
        // 如果密钥长度不为 16、24 或 32 字节，则进行填充或截断
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        if (keyBytes.length == 16 || keyBytes.length == 24 || keyBytes.length == 32) {
            return keyBytes;
        } else if (keyBytes.length < 16) {
            // 填充至 16 字节
            return java.util.Arrays.copyOf(keyBytes, 16);
        } else if (keyBytes.length < 24) {
            // 填充至 24 字节
            return java.util.Arrays.copyOf(keyBytes, 24);
        } else {
            // 填充至 32 字节
            return java.util.Arrays.copyOf(keyBytes, 32);
        }
    }

    public static String doAesECBEncrypt(String plainText, String key) {
        if (StringUtils.isEmpty(plainText)) {
            return null;
        }
        try {
            byte[] keyBytes = getAESKey(key);
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            return plainText;
        }
    }

    public static String doAesECBDecrypt(String encryptText, String key) {
        try {
            byte[] keyBytes = getAESKey(key);
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            byte[] decoded = Base64.getDecoder().decode(encryptText);
            byte[] decrypted = cipher.doFinal(decoded);
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.info("AES-ECB解密失败{}", encryptText);
            return encryptText;
        }
    }

    private static byte[] cipherData(PaddedBufferedBlockCipher engine, byte[] data) throws InvalidCipherTextException {
        int outputSize = engine.getOutputSize(data.length);
        byte[] outBuf = new byte[outputSize];
        int processBytes = engine.processBytes(data, 0, data.length, outBuf, 0);
        int finalBytes = engine.doFinal(outBuf, processBytes);
        byte[] encoded = new byte[processBytes + finalBytes];
        System.arraycopy(outBuf, 0, encoded, 0, encoded.length);
        return encoded;
    }

    private static CipherParameters genAESCipherParameters() {
        return new ParametersWithIV(new
                KeyParameter(AES_CFB_KEY.getBytes(StandardCharsets.UTF_8)), "".getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 加密方法（Sm2 的专门针对前后端分离，非对称秘钥对的方式，暴露出去的公钥，对传输过程中的密码加个密）
     *
     * @param str 待加密数据
     * @return 加密后的密文
     * @author yubaoshan
     * @date 2022/9/15 21:51
     */
    public static String doSm2Encrypt(String str) {
        return Sm2.doEncrypt(str, PUBLIC_KEY);
    }

    /**
     * 解密方法
     * 如果采用加密机的方法，用try catch 捕捉异常，返回原文值即可
     *
     * @param str 密文
     * @return 解密后的明文
     * @author yubaoshan
     * @date 2022/9/15 21:51
     */
    public static String doSm2Decrypt(String str) {
        // 解密
        return Sm2.doDecrypt(str, PRIVATE_KEY);
    }

    /**
     * 加密方法
     *
     * @param str 待加密数据
     * @return 加密后的密文
     * @author yubaoshan
     * @date 2022/9/15 21:51
     */
    public static String doSm4CbcEncrypt(String str) {
        // SM4 加密  cbc模式
        Sm4Options sm4Options4 = new Sm4Options();
        sm4Options4.setMode("cbc");
        sm4Options4.setIv("fedcba98765432100123456789abcdef");
        return Sm4.encrypt(str, KEY, sm4Options4);
    }

    /**
     * 解密方法
     * 如果采用加密机的方法，用try catch 捕捉异常，返回原文值即可
     *
     * @param str 密文
     * @return 解密后的明文
     * @author yubaoshan
     * @date 2022/9/15 21:51
     */
    public static String doSm4CbcDecrypt(String str) {
        // 解密，cbc 模式，输出 utf8 字符串
        Sm4Options sm4Options8 = new Sm4Options();
        sm4Options8.setMode("cbc");
        sm4Options8.setIv("fedcba98765432100123456789abcdef");
        String docString = Sm4.decrypt(str, KEY, sm4Options8);
        if ("".equals(docString)) {
            log.warn(">>> 字段解密失败，返回原文值：{}", str);
            return str;
        } else {
            return docString;
        }
    }

    /**
     * 纯签名
     *
     * @param str 待签名数据
     * @return 签名结果
     * @author yubaoshan
     * @date 2022/9/15 21:51
     */
    public static String doSignature(String str) {
        return Sm2.doSignature(str, PRIVATE_KEY);
    }

    /**
     * 验证签名结果
     *
     * @param originalStr 签名原文数据
     * @param str         签名结果
     * @return 是否通过
     * @author yubaoshan
     * @date 2022/9/15 21:51
     */
    public static boolean doVerifySignature(String originalStr, String str) {
        return Sm2.doVerifySignature(originalStr, str, PUBLIC_KEY);
    }

    /**
     * 通过杂凑算法取得hash值，用于做数据完整性保护
     *
     * @param str 字符串
     * @return hash 值
     * @author yubaoshan
     * @date 2022/9/15 21:51
     */
    public static String doHashValue(String str) {
        return Sm3.sm3(str);
    }

    public static String doMd5(String text) {
        if (text == null || "".equals(text.trim())) {
            throw new IllegalArgumentException("请输入要加密的内容");
        }
        String encryptText = null;
        try {
            MessageDigest m = MessageDigest.getInstance("md5");
            m.update(text.getBytes(StandardCharsets.UTF_8));
            byte[] arr = m.digest();
            StringBuilder sb = new StringBuilder(32);
            for (int i = 0; i < arr.length; ++i) {
                String hex = Integer.toHexString(arr[i] & 0xFF);
                if (hex.length() < 2) {
                    sb.append('0');
                }
                sb.append(hex);
            }
            return sb.toString();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return encryptText;
    }
}
